/*
 *  Socket.c
 *  jscsh
 *
 *  Created by Jonathan Hohle on 1/15/08.
 *  Copyright 2008 hohle.net. All rights reserved.
 *
 */

#include "Socket.h"
#include "util.h"
#include <sys/socket.h>
#include <netdb.h>

typedef struct _SocketPrivate {
    int domain;
    int type;
    int protocol;
    int socket;
} SocketPrivate;


void objectAddIntegerConstant(JSContextRef context, JSObjectRef socketObject, char* name, int value)
{
    JSStringRef nameString = JSStringCreateWithUTF8CString(name);
    JSObjectSetProperty(context,
                        socketObject,
                        nameString,
                        JSValueMakeNumber(context, value),
                        kJSPropertyAttributeReadOnly,
                        NULL);
    JSStringRelease(nameString);
}

void Socket_extendHost(JSContextRef context, JSObjectRef contextGlobalObject)
{
    JSStringRef socketString = JSStringCreateWithUTF8CString("Socket");
    JSObjectSetProperty(context,
                        contextGlobalObject,
                        socketString,
                        JSObjectMakeConstructor(context,
                                                Socket_class(context),
                                                Socket_construct),
                        kJSPropertyAttributeNone,
                        NULL);
    
    JSValueRef socketRef = JSObjectGetProperty(context, contextGlobalObject, socketString, NULL);
    JSObjectRef socketObject = JSValueToObject(context, socketRef, NULL);
    JSStringRelease(socketString);
    
    //
    // Class Functions
    //
    JSObjectAddClassMethod(context, socketObject, "getaddrinfo", Socket_s_getaddrinfo, kJSPropertyAttributeNone);
    
    //
    // Constants
    //
    objectAddIntegerConstant(context, socketObject, "AF_INET", AF_INET);
    objectAddIntegerConstant(context, socketObject, "AF_INET6", AF_INET6);
    objectAddIntegerConstant(context, socketObject, "AF_UNSPEC", AF_UNSPEC);
    
    objectAddIntegerConstant(context, socketObject, "SOCK_STREAM", SOCK_STREAM);
    objectAddIntegerConstant(context, socketObject, "SOCK_DGRAM", SOCK_DGRAM);
    objectAddIntegerConstant(context, socketObject, "SOCK_MAXADDRLEN", SOCK_MAXADDRLEN);
    objectAddIntegerConstant(context, socketObject, "SOCK_RAW", SOCK_RAW);
    objectAddIntegerConstant(context, socketObject, "SOCK_SEQPACKET", SOCK_SEQPACKET);
    
    objectAddIntegerConstant(context, socketObject, "IPPROTO_IP", IPPROTO_IP);
    objectAddIntegerConstant(context, socketObject, "IPPROTO_ICMP", IPPROTO_ICMP);
    objectAddIntegerConstant(context, socketObject, "IPPROTO_IGMP", IPPROTO_IGMP);
    objectAddIntegerConstant(context, socketObject, "IPPROTO_GGP", IPPROTO_GGP);
    objectAddIntegerConstant(context, socketObject, "IPPROTO_TCP", IPPROTO_TCP);
    objectAddIntegerConstant(context, socketObject, "IPPROTO_EGP", IPPROTO_EGP);
    objectAddIntegerConstant(context, socketObject, "IPPROTO_PUP", IPPROTO_PUP);
    objectAddIntegerConstant(context, socketObject, "IPPROTO_UDP", IPPROTO_UDP);
    objectAddIntegerConstant(context, socketObject, "IPPROTO_IDP", IPPROTO_IDP);
    objectAddIntegerConstant(context, socketObject, "IPPROTO_HELLO", IPPROTO_HELLO);
    objectAddIntegerConstant(context, socketObject, "IPPROTO_ND", IPPROTO_ND);
    objectAddIntegerConstant(context, socketObject, "IPPROTO_TP", IPPROTO_TP);
    objectAddIntegerConstant(context, socketObject, "IPPROTO_XTP", IPPROTO_XTP);
    objectAddIntegerConstant(context, socketObject, "IPPROTO_EON", IPPROTO_EON);
    // objectAddIntegerConstant(context, SocketObject, "IPPROTO_BIP", IPPROTO_BIP);
    objectAddIntegerConstant(context, socketObject, "IPPROTO_RAW", IPPROTO_RAW);
    objectAddIntegerConstant(context, socketObject, "IPPROTO_MAX", IPPROTO_MAX);
}

//
// Private Methods
//
char* servForPort(unsigned int port)
{
    // todo, return a service name for the port...
}

//
// Class Methods
//

//
// Socket.getaddrinfo(host, service, family=nil, socktype=nil, protocol=nil, flags=nil) => addrinfo
//
JSValueRef Socket_s_getaddrinfo(JSContextRef context, JSObjectRef object, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
{
    if (argumentCount < 2)
    {
        *exception = JSValueMakeNumber(context, 1);
        return NULL;
    }
    char* host = UTF8CStringFromJSValueRef(context, arguments[0]);
    char* servname = (JSValueIsNumber(context, arguments[1])) ?
        servForPort(JSValueToNumber(context, arguments[1], NULL)) :
        UTF8CStringFromJSValueRef(context, arguments[1]);
    
    struct addrinfo hints = {NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL};

    if (argumentCount >= 3)
    {
        if (!JSValueIsNumber(context, arguments[2]))
        {
            *exception = JSValueMakeNumber(context, 1);
            return NULL;
        }
        hints.ai_family = (int) JSValueToNumber(context, arguments[2], NULL);
    }
    if (argumentCount >= 4)
    {
        if (!JSValueIsNumber(context, arguments[3]))
        {
            *exception = JSValueMakeNumber(context, 1);
            return NULL;
        }
        hints.ai_socktype = (int) JSValueToNumber(context, arguments[2], NULL);
    }
}

JSValueRef Socket_s_gethostbyname(JSContextRef context, JSObjectRef object, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
JSValueRef Socket_s_getservbyname(JSContextRef context, JSObjectRef object, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);

//
// Constructor/Destructor
//
JSClassRef Socket_class(JSContextRef context)
{
    JSStaticFunction staticFunctions[] = {
        // {"getaddrinfo", Socket_s_getaddrinfo, kJSPropertyAttributeNone},
        {0, 0, 0}
    };
    
    JSClassDefinition socketDefinition = kJSClassDefinitionEmpty;
    socketDefinition.className = "Socket";
    socketDefinition.attributes = kJSClassAttributeNone;
    socketDefinition.staticFunctions = staticFunctions;
    socketDefinition.initialize = Socket_initialize;
    socketDefinition.finalize = Socket_finalize;
    
    JSClassRef socketClass = JSClassCreate(&socketDefinition);
    
    return socketClass;
}

JSObjectRef Socket_construct(JSContextRef context, JSObjectRef object, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
{
    if (argumentCount != 3)
    {
        *exception = JSValueMakeNumber(context, 1);
        return NULL;
    }
    
    SocketPrivate* socketPrivate = malloc(sizeof(SocketPrivate));
    JSClassRef socketClass = Socket_class(context);
    JSObjectRef socketObject = JSObjectMake(context, socketClass, socketPrivate);
    return socketObject;
}
static void Socket_initialize(JSContextRef context, JSObjectRef object)
{
    SocketPrivate* socketPrivate = JSObjectGetPrivate(object);
    printf("initializing a socket!");
}

static void Socket_finalize(JSObjectRef object)
{
    SocketPrivate* socketPrivate = JSObjectGetPrivate(object);
    free(socketPrivate);
    printf("goodbye socket!");
}

//
// Instance Methods
//
JSValueRef Socket_i_accept(JSContextRef context, JSObjectRef object, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
JSValueRef Socket_i_acceptNonBlock(JSContextRef context, JSObjectRef object, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
JSValueRef Socket_i_bind(JSContextRef context, JSObjectRef object, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
JSValueRef Socket_i_connect(JSContextRef context, JSObjectRef object, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
JSValueRef Socket_i_connectNonBlock(JSContextRef context, JSObjectRef object, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
JSValueRef Socket_i_listen(JSContextRef context, JSObjectRef object, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
JSValueRef Socket_i_recvfrom(JSContextRef context, JSObjectRef object, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
JSValueRef Socket_i_recvfromNonBlock(JSContextRef context, JSObjectRef object, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
JSValueRef Socket_i_sysAccept(JSContextRef context, JSObjectRef object, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
